/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.jwt.config;

import cn.star.framework.autoconfigure.jwt.TokenProperties;
import cn.star.framework.autoconfigure.security.SecurityProperties;
import cn.star.framework.jwt.core.JwtTokenProvider;
import cn.star.framework.jwt.core.SecurityPasswordEncoder;
import cn.star.framework.jwt.filter.InvalidAuthenticationEntryPoint;
import cn.star.framework.jwt.filter.JwtTokenOncePerRequestFilter;
import cn.star.framework.jwt.security.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Spring Security认证配置<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

  @Autowired private JwtTokenProvider provider;

  @Autowired private UserDetailsService userDetailsService;

  @Autowired private InvalidAuthenticationEntryPoint invalidAuthenticationEntryPoint;

  @Autowired private SecurityPasswordEncoder passwordEncoder;

  @Autowired private SecurityProperties security;

  @Autowired private TokenService tokenService;

  @Autowired private TokenProperties token;

  @Bean
  public SecurityFilterChain filterChain(
      HttpSecurity http, AuthenticationProvider authenticationProvider) throws Exception {
    http
        // 禁用basic明文验证
        .httpBasic()
        .disable()
        // 前后端分离架构不需要csrf保护
        .csrf()
        .disable()
        // 禁用默认登录页
        .formLogin()
        .disable()
        // 禁用默认登出页
        .logout()
        .disable()
        // 设置异常的EntryPoint，如果不设置，默认使用 Http403ForbiddenEntryPoint
        .exceptionHandling(
            exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))
        // 前后端分离是无状态的，不需要session了，直接禁用。
        .sessionManagement(
            session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests()
        // 使用 config 中定义进行配置
        // .antMatchers("/authenticate", "/refresh_token").permitAll()
        // .antMatchers("/error/**").permitAll()
        .antMatchers(security.getDefaultAntPatterns())
        .permitAll()
        .antMatchers(security.getWebIgnoreDefaultAntPatterns())
        .permitAll()
        .antMatchers(security.getWebIgnoreAntPatterns())
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .authenticationProvider(authenticationProvider)
        .addFilterBefore(
            new JwtTokenOncePerRequestFilter(provider, tokenService, security, token),
            UsernamePasswordAuthenticationFilter.class);

    return http.build();
  }

  @Bean
  public WebSecurityCustomizer webSecurityCustomizer() {
    return web ->
        web.ignoring()
            .requestMatchers(CorsUtils::isPreFlightRequest)
            // 在 HttpSecurity 中配置，不然启动会出现以下警告
            // You are asking Spring Security to ignore Ant [pattern='doc.html']. This is not recommended --
            // please use permitAll via HttpSecurity#authorizeHttpRequests instead.
            // Will not secure Ant [pattern='doc.html']
            // 会出现以上的警告，但是不影响；在此处配置不会过 JwtTokenOncePerRequestFilter 过滤器
            // .antMatchers(config.getWebIgnoreDefaultAntPatterns())
            // .antMatchers(config.getWebIgnoreAntPatterns())
        ;
  }

  /**
   * 调用loadUserByUsername获得UserDetails信息<br>
   * 在{@link AbstractUserDetailsAuthenticationProvider}里执行用户状态检查
   *
   * @return
   */
  @Bean
  public AuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailsService);
    provider.setPasswordEncoder(passwordEncoder);

    return provider;
  }

  /**
   * 登录时需要调用{@link AuthenticationManager#authenticate(Authentication)}执行一次校验
   *
   * @param config
   * @return
   * @throws Exception
   */
  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
      throws Exception {
    return config.getAuthenticationManager();
  }

  /**
   * 跨域配置
   *
   * @return
   */
  @Bean
  public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry
            .addMapping("/**")
            .allowCredentials(true)
            .allowedOriginPatterns("*")
            .allowedHeaders("*")
            .allowedMethods("*");
      }
    };
  }
}
